/*
 * Ipnet.c
 *
 *  Created on: Oct 15, 2018
 *      Author: ax
 */
#define USE_ETH_OOB
#define MODULE "[Ipnet]: "

#include <string.h>

#include <Log.h>
#include <Timer.h>
#include <Device.h>
#include <Hw/Netcp.h>
#include <Hw/Memory.h>
#include <Socket/Ipnet.h>
#include <Socket/Ipnet/Udp.h>
#include <Socket/Link/Ethernet.h>



#define ARP_PROTOCOL    BE16(0x0806)
#define ARP_REQUEST     BE16(0x0001)
#define ARP_RESPONSE    BE16(0x0002)
#define ARP_HW_ETH      BE16(0x0001)

#define POTCL_IP4N      BE16(0x0800)
#define IP_VER_LEN      0x45
#define UDP_PROTOCOL    0x11

#define Udp_MTU         (Ip_MTU - IP_HDR_SIZE - sizeof(UDPHDR))

extern Uint8 Netcp_Buffer[][Netcp_BUFFER_SIZE];


struct  {
    Ipn4    IPSrc;
    Ipn4    IPDst;
    Uint16  Length;
    Uint8   Null;
    Uint8   Protocol;
} Pseudo[2];
#define _TX     0
#define _RX     1
#define _PEER   1

ArpEntry Eth_ARP_TABLE[Eth_ARP_NUM];

STATIC Ethernet_Frame __buffer(arp) Arp_Packet/*[2]*/ = {
    //{//ARP request packet
     .Head.Type = ARP_PROTOCOL,
     .Head.Dst.Val={.H = 0xffff, .L = 0xffffffff},
     .Packet.Arp = {
                .HardType     = ARP_HW_ETH,
                .ProtocolType = POTCL_IP4N,
                .HardSize     = Eth_MAC_SIZE,
                .ProtocolSize = sizeof(Ipn4),
                .Op           = ARP_REQUEST,
              /*.DMac.Val  = {.H = 0xffff, .L = 0xffffffff}*/  }  }/*,
    {.Head.Type = ARP_PROTOCOL,
     .Packet.Arp = {
}   }   }*/;


#ifdef _DBG
static
void
Netcp_descriptorInfo(
    const char* info
){
    Log_dbg(MODULE"Pool - TXQ: %d TCQ: %d FDQ: %d RXQ: %d -- %s.\n",
            Qmss_getEntryCnt(Netcp_QPSSW), Qmss_getEntryCnt(Netcp_TCQ),
            Qmss_getEntryCnt(Netcp_FDQ), Qmss_getEntryCnt(Netcp_RXQ), info);
}

bool
Netcp_invalidBuffer(
    void*       xxx,
    const char* msg
){
    EthFrame ef = EthFrame_align(xxx);

    if ((NULL != ef) && (ef->Oob.Ext.Magic == (Ptr) ef))
        return false;
    //else
    Log_warn(MODULE"XXX %s.\n", msg);
    return true;
}
#else
#define Netcp_descriptorInfo(info)      do {} while (0)
#define Netcp_invalidBuffer(x, ...)     false
#endif


static inline
Udp_Status
Mac_send(
    Ptr     ef,
    Uint16  size
){
    if (Netcp_invalidBuffer(ef, "invalid eth-frame to send"))
        return Udp_INVALID_MSG_BUFF;

    size += Eth_HDR_SIZE;

    Uh("send as following sequence");
    while (Qmss_getEntryCnt(Netcp_QPSSW) >= Netcp_TX_THRESHOLD);
    HostDesc hd = (HostDesc) Qmss_pop(Netcp_TCQ);
    if (NULL == hd) {
        Log_warn(MODULE"XXX descriptor lost found in send.\n");
        return Udp_TX_BD_LOST;
    }

    hd->packet_length = hd->orig_buff0_len
            = (size < Eth_SIZE_MIN) ? Eth_SIZE_MIN : size;
    hd->buffer_ptr = hd->orig_buff0_ptr = (uint32_t) ef;

    Qmss_push(Netcp_QPSSW, (Descriptor) hd | Netcp_DescSize);

    return Udp_SOK;
}

static inline
EthFrame
Mac_receive(
    void
){
    EthFrame ef;
    HostDesc hd;

    hd = (HostDesc) Qmss_pop(Netcp_RXQ);
    if (NULL == hd)
        return NULL;

    ef = EthFrame_align(hd->buffer_ptr);
    if (Netcp_invalidBuffer(ef, "invalid free msg"))
        return NULL;

    return ef;
}


static inline
Udp_Status
Mac_free(
    void* xxx
){
    if (Netcp_invalidBuffer(xxx, "invalid free msg"))
        return Udp_INVALID_MSG_BUFF;

    EthFrame ef = EthFrame_align(xxx);

    Qmss_push(Netcp_FDQ, ef->Oob.Ext.Descriptor);
    return Udp_SOK;
}


Udp_Status
Udp_open(
    Ipn ipLocal,
    Ipn ipPeer
){
    Log_dbg(MODULE"Connect IPN-%x with self-%x.\n", ipPeer, ipLocal);

    Pseudo[_TX].Null    = Pseudo[_RX].Null      = 0;
    Pseudo[_TX].IPSrc   = Pseudo[_RX].IPDst     = ipLocal;
    Pseudo[_TX].IPDst   = Pseudo[_RX].IPSrc     = ipPeer;
    Pseudo[_TX].Protocol= Pseudo[_RX].Protocol  = 0x11;

    //record known info.
    Eth_ARP_TABLE[_PEER].Ip = ipPeer;
    Eth_ARP_TABLE[_SELF].Ip = ipLocal;


    //prepare to populate an arp request action
    #ifdef USE_ETH_OOB
    Arp_Packet.Oob.Ext.Magic = &Arp_Packet;
    #endif
    Arp_Packet.Packet.Arp.DIp4 = ipPeer;
    Arp_Packet.Packet.Arp.SIp4 = ipLocal;
    Arp_Packet.Packet.Arp.SMac.Val.H
            = Arp_Packet.Head.Src.Val.H
            = Eth_ARP_TABLE[_SELF].Mac.Val.H;
    Arp_Packet.Packet.Arp.SMac.Val.L
            = Arp_Packet.Head.Src.Val.L
            = Eth_ARP_TABLE[_SELF].Mac.Val.L;

    bool     done = false;
    EthFrame ef;
    Int16    r;


    //try ARP request
    for (r = 0; (!done) && (r < Udp_OpenRetry); r++) {
        Mac_send((Ptr) &Arp_Packet.Head, Eth_SIZE_MIN);

        Timestamp to = Timer_getStamp() + Udp_OpenTimeout;
        do {
            if ((ef = Mac_receive()) == NULL) {
                Netcp_descriptorInfo("ARP no replay");
                nop(4000000);
                continue;
            }

            if ((ef->Head.Type != ARP_PROTOCOL)				/* ignore below: */
                || (ef->Packet.Arp.Op != ARP_RESPONSE)          				/* not an ARP response*/
                || (ef->Packet.Arp.DIp4 != ipLocal)              				/* not to this request */
                || ((ef->Head.Dst.Val.H != Eth_ARP_TABLE[_SELF].Mac.Val.H)	    /* self request looped back */
                    && (ef->Head.Dst.Val.L != Eth_ARP_TABLE[_SELF].Mac.Val.L)))
            {   Netcp_descriptorInfo("replay not desired");
                Mac_free(ef);

                continue;
            }

            done = true;
            to = 0;//force timeout
        } while (to > Timer_getStamp());
    }


    if (!done)//timeout!
        return Udp_OPEN_ARP_TIMEOUT;

    Uh("update ARP record");
    Eth_ARP_TABLE[_PEER].Mac.Val.H = ef->Head.Src.Val.H;
    Eth_ARP_TABLE[_PEER].Mac.Val.L = ef->Head.Src.Val.L;

    Netcp_descriptorInfo("open ok");
    Log_mark(MODULE" UDP network linkup, peer MAC: %0x%0x-%0x%x-%0x%0x.\n",
         ef->Head.Dst.Hex[0], ef->Head.Dst.Hex[1], ef->Head.Dst.Hex[2],
         ef->Head.Dst.Hex[3], ef->Head.Dst.Hex[4], ef->Head.Dst.Hex[5]);

    Mac_free(ef);


    Uh("update transmit stack");
    for (r = 0; r < Netcp_TXBD_CNT + 1; r++) {
        ef = (EthFrame) &Netcp_Buffer[r][0];

        ef->Head.Src.Val.H = Eth_ARP_TABLE[_SELF].Mac.Val.H;
        ef->Head.Src.Val.L = Eth_ARP_TABLE[_SELF].Mac.Val.L;
        ef->Head.Dst.Val.H = Eth_ARP_TABLE[_PEER].Mac.Val.H;
        ef->Head.Dst.Val.L = Eth_ARP_TABLE[_PEER].Mac.Val.L;
        ef->Head.Type = POTCL_IP4N;

        ef->Packet.Ip4.Head.Tos = 0;
        ef->Packet.Ip4.Head.Ttl = 64;
        ef->Packet.Ip4.Head.FlagOff = 0;
        ef->Packet.Ip4.Head.VerLen = IP_VER_LEN;
        ef->Packet.Ip4.Head.Protocol = UDP_PROTOCOL;
        ef->Packet.Ip4.Head.IPSrc = Pseudo[_TX].IPSrc;
        ef->Packet.Ip4.Head.IPDst = Pseudo[_TX].IPDst;
    };

    Uh("ARP request not used any more, reused it as arp_response packet");
    Arp_Packet.Packet.Arp.Op = ARP_RESPONSE;

    return Udp_SOK;
}

static
Uint16
Udp_checksum(
    Udp_Msg ptr_udphdr,
    Uint16  length,
    Uint16  dir
){
    Uint16* pw;
    Uint32  TSum;

    /* Checksum field is NULL in checksum calculations */
    ptr_udphdr->UDPChecksum = 0;

    /* Checksum the header */
    pw = (Uint16 *)ptr_udphdr;
    for(TSum = 0; length > 1; length -= 2 )
        TSum += (Uint32)*pw++;
#ifdef _BIGENDIAN
    if( length )
        TSum += (Uint32)(*pw & 0xFF00);
#else
    if( length )
        TSum += (Uint32)(*pw & 0x00FF);
#endif

    /* Checksum the pseudo header */
    pw = (Uint16 *)&Pseudo[dir];
    for( length=0; length < 6; length++ )
        TSum += (Uint32)*pw++;

    TSum = (TSum&0xFFFF) + (TSum>>16);
    TSum = (TSum&0xFFFF) + (TSum>>16);

    /* Special case the 0xFFFF checksum
     *  - don't use a checksum value of 0x0000 */
    if( TSum != 0xFFFF )
        TSum = ~TSum;

    return TSum;
}

/**
 *  @b Description
 *  @n
 *       The function computes the IP checksum. The computed checksum
 *       is populated in the IP header.
 *
 *  @param[in]  ptr_iphdr
 *      This is the pointer to the IPv4 header for which the checksum
 *      is computed.
 *
 *  @retval
 *      Not Applicable.
 */
static
void
Ipv4_checksum(
    Ip4Head *ptr_iphdr
){
    Int32   tmp1;
    Uint16  *pw;
    Uint32  TSum = 0;

    /* Get header size in 4 byte chunks */
    tmp1 = ptr_iphdr->VerLen & 0xF;

    /* Checksum field is NULL in checksum calculations */
    ptr_iphdr->Checksum = 0;

    /* Checksum the header */
    pw = (Uint16 *)ptr_iphdr;
    do {
        TSum += (Uint32)*pw++;
        TSum += (Uint32)*pw++;
    } while( --tmp1 );
    TSum = (TSum&0xFFFF) + (TSum>>16);
    TSum = (TSum&0xFFFF) + (TSum>>16);
    TSum = ~TSum;

    /* Note checksum is Net/Host byte order independent */
    ptr_iphdr->Checksum = (Uint16)TSum;
    return;
}


Udp_Msg
Udp_alloc(
    void
){
    static unsigned cnt = 0;

    EthFrame frame = (EthFrame) &Netcp_Buffer[cnt++ & (Netcp_TXBD_CNT - 1)][0];
    return (Udp_Msg) &((EthFrame) frame)->Packet.Ip4.Datgrm.Udp;
}


Udp_Status
Udp_send(
    Udp_Msg msg
){
    static Uint16 idx = 0;

    Uint16 size = msg->Length;
    if (Udp_MTU < size)
        return Udp_PACKET_MTU_OV;

    uint16_t p = msg->SrcPort; msg->SrcPort = BE16(p);
             p = msg->DstPort; msg->DstPort = BE16(p);
    size += UDPHDR_SIZE;
    msg->Length = Pseudo[_TX].Length = BE16(size);
    msg->UDPChecksum = Udp_checksum(msg, size, _TX);

    EthFrame ef = EthFrame_align(msg);

    size += IP_HDR_SIZE;
    Ip4Pkt pkt = (Ip4Pkt) &(ef->Packet.Ip4);
    pkt->Head.TotalLen = BE16(size);
    pkt->Head.Id = idx++;
    Ipv4_checksum(&pkt->Head);

    return Mac_send(&ef->Head, size);
}

static void Arp_response(Arp arp) {
    if ((arp->DIp4 != Pseudo[_TX].IPSrc)
        || (arp->Op != ARP_REQUEST)) {
        Uh("[ARP]: not match: IP-%x OP-%x\n", arp->DIp4, arp->Op);
    } else {
        Uh("[ARP]: response.\n");
        Arp_Packet.Packet.Arp.DIp4 = arp->SIp4;
        Arp_Packet.Packet.Arp.DMac.Val.H
            = Arp_Packet.Head.Dst.Val.H = arp->SMac.Val.H;
        Arp_Packet.Packet.Arp.DMac.Val.L
            = Arp_Packet.Head.Dst.Val.L = arp->SMac.Val.L;
        Mac_send(&Arp_Packet.Head, Eth_SIZE_MIN);
    }

    Mac_free(arp);
}


Udp_Msg
Udp_receive(
    void
){
    EthFrame ef;

    Netcp_receive:
    if ((ef = Mac_receive()) == NULL)
        return NULL;


    if (ef->Head.Type == ARP_PROTOCOL) {
        Arp_response(&ef->Packet.Arp);
        goto Netcp_receive;
    } else if ((ef->Packet.Ip4.Head.IPDst != Pseudo[_TX].IPSrc)
            || (ef->Head.Type != POTCL_IP4N)
            || (ef->Packet.Ip4.Head.Protocol != UDP_PROTOCOL)
            || (ef->Packet.Ip4.Head.VerLen != IP_VER_LEN)
          /*||(xxx OTHER drop situation )*/) {
        Uh("Uusupported packet, drop.\n");
    } else {
	    Udp_Msg msg = (Udp_Msg) &ef->Packet.Ip4.Datgrm.Udp.Head;
	    Uint16 chksum = msg->UDPChecksum;
	    Uint16 size = Pseudo[_RX].Length = msg->Length;
        size = BE16(size);
        if (chksum == Udp_checksum(msg, size, _RX)) {
            msg->Length = size - UDPHDR_SIZE;
            size = msg->DstPort; msg->DstPort = BE16(size);
            size = msg->SrcPort; msg->SrcPort = BE16(size);
            return msg;
        } else
            Uh("[Udp]: invalid checksum.\n");
    }

    Udp_free((Udp_Msg) ef);
    return NULL;
}


void
Udp_free(
    Udp_Msg msg
){
    Mac_free(msg);
}

